package cn.sc.summer.mybatis.scan;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.sc.summer.constant.mybatisplus.MybatisConstant;
import cn.sc.summer.mybatis.annotation.table.TableFieldX;
import cn.sc.summer.mybatis.annotation.table.TableX;
import cn.sc.summer.mybatis.constant.TableResource;
import cn.sc.summer.mybatis.model.DataSourceColumnType;
import cn.sc.summer.mybatis.model.DataSourceX;
import cn.sc.summer.mybatis.model.TableFieldXInfo;
import cn.sc.summer.mybatis.model.TableXInfo;
import cn.sc.summer.mybatis.util.ScanUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.google.common.base.CaseFormat;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 类名：扫描所有数据库表实体类映射信息，进行数据库表字段信息更新或新建数据库表
 *
 * @author a-xin
 * @date 2024/8/23 16:14
 */
@Slf4j
public class JavaTableScanner {

    /**
     * 扫描所有实体类表信息，进行表数据信息封装
     */
    public static void scanEntityInfo(List<DataSourceX> allDataSource, String[] scanPackage) {
        Set<Class<?>> annotation = new HashSet<>();
        if (scanPackage == null || scanPackage.length < 1) {
            annotation.addAll(ScanUtil.getAnnotation(MybatisConstant.PROJECT_PACKAGE, TableX.class));
        } else {
            Arrays.stream(scanPackage).forEach(ckage -> {
                if (CharSequenceUtil.isNotBlank(ckage)) {
                    annotation.addAll(ScanUtil.getAnnotation(ckage, TableX.class));
                }
            });
        }
        if (CollUtil.isNotEmpty(annotation)) {
            annotation.stream().forEach(tableXInfo -> getTableXInfo(tableXInfo, allDataSource));
        }
    }

    /**
     * 获取数据库实体类表信息
     *
     * @param clazz class字节码文件
     */
    public static void getTableXInfo(Class<?> clazz, List<DataSourceX> allDataSource) {

        TableX tableX = clazz.getAnnotation(TableX.class);
        List<String> dataSourceNames = Arrays.stream(tableX.dataSource()).map(String::toLowerCase)
                .collect(Collectors.toList());

        Map<String, String> dataSourceTypeMap = allDataSource.stream()
                .collect(Collectors.toMap(dataSourceX -> dataSourceX.getDataSourceName().toLowerCase(),
                        dataSourceX -> dataSourceX.getDataSourceTypeName().toLowerCase()));

        dataSourceNames.stream().forEach(dataSourceName -> {
            if (!dataSourceTypeMap.containsKey(dataSourceName)) {
                throw new IllegalArgumentException("Unsupported data source types: " + dataSourceName);
            }
        });

        dataSourceNames.stream().forEach(dataSourceName -> {

            TableXInfo tableXInfo = new TableXInfo();
            TableName tableName = clazz.getAnnotation(TableName.class);
            if (ObjectUtil.isNull(tableName)) {
                String simpleName = clazz.getSimpleName();
                tableXInfo.setTableName(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, simpleName));
            } else {
                tableXInfo.setTableName(tableName.value());
            }
            tableXInfo.setComment(tableX.comment());

            List<Field> allField = getAllField(clazz);
            List<TableFieldXInfo> fieldInfo = getFieldXInfo(allField, dataSourceTypeMap.get(dataSourceName));

            if (CollUtil.isEmpty(fieldInfo)) {
                throw new IllegalArgumentException("The table " + tableXInfo.getTableName() + " fields can not be empty!");
            }

            long primaryCount = fieldInfo.stream().filter(item -> item.getPrimary().equals(Boolean.TRUE))
                    .count();
            if (primaryCount < 1) {
                throw new IllegalArgumentException("The primary key is not set for the data source table: " + tableXInfo.getTableName());
            }

            List<TableFieldXInfo> fieldXInfo = fieldInfo.stream().peek(field -> {
                field.setTableName(tableXInfo.getTableName());
            }).collect(Collectors.toList());
            tableXInfo.setTableFieldXInfos(fieldXInfo);


            if (TableResource.TABLE_INFO.containsKey(dataSourceName)) {
                List<TableXInfo> tableXInfos = TableResource.TABLE_INFO.get(dataSourceName);
                tableXInfos.add(tableXInfo);
                TableResource.TABLE_INFO.put(dataSourceName, tableXInfos);
            } else {
                TableResource.TABLE_INFO.put(dataSourceName, ListUtil.of(tableXInfo));
            }
        });
    }

    /**
     * 获取class的所有字段信息(包含所有的父级字段信息)
     *
     * @param clazz 字节码文件
     * @return 所有字段信息
     */
    public static List<Field> getAllField(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        Class<?> tempClass = clazz;
        while (ObjectUtil.isNotNull(tempClass) && !tempClass.equals(Object.class)) {
            fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
            tempClass = tempClass.getSuperclass();
        }
        return fieldList;
    }

    /**
     * 获取表字段信息
     *
     * @param fields 字段信息
     * @return 字段信息
     */
    public static List<TableFieldXInfo> getFieldXInfo(List<Field> fields, String dataSourceType) {
        if (CollUtil.isEmpty(fields)) {
            return new ArrayList<>();
        }

        List<TableFieldXInfo> tableFieldXInfos = new ArrayList<>();
        for (Field field : fields) {
            if (field.getName().equals("serialVersionUID")) {
                continue;
            }

            TableFieldXInfo tableFieldXInfo = new TableFieldXInfo();
            TableField tableField = field.getAnnotation(TableField.class);
            if (ObjectUtil.isNotNull(tableField)) {
                if (!tableField.exist()) {
                    continue;
                }
                tableFieldXInfo.setFieldName(tableField.value());
            } else {
                tableFieldXInfo.setFieldName(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName()));
            }

            TableFieldX tableFieldX = field.getAnnotation(TableFieldX.class);
            tableFieldXInfo.setPrimary(ObjectUtil.isNull(tableFieldX) ? Boolean.FALSE : tableFieldX.primary());
            tableFieldXInfo.setComment(ObjectUtil.isNull(tableFieldX) ? "" : tableFieldX.comment());
            tableFieldXInfo.setNullable(ObjectUtil.isNull(tableFieldX) || tableFieldX.nullable());

            String fieldType = getFieldType(ObjectUtil.isNull(tableFieldX) ? "" : tableFieldX.fieldType(),
                    field.getType(),
                    ObjectUtil.isNull(tableFieldX) ? -1 : tableFieldX.length(),
                    dataSourceType);
            tableFieldXInfo.setFieldType(fieldType);

            tableFieldXInfos.add(tableFieldXInfo);
        }
        return tableFieldXInfos;
    }

    /**
     * 获取字段对应数据源类型
     *
     * @param fieldType      注解定义的字段类型
     * @param fieldTypeClass 字段本身类型
     * @param length         注解定义字段类型长度
     * @param dataSourceType 数据源类型
     * @return 字段类型
     */
    private static String getFieldType(String fieldType, Class<?> fieldTypeClass, Integer length, String dataSourceType) {
        StringBuilder stringBuilder = new StringBuilder();

        List<DataSourceColumnType> columnType = getColumnType(dataSourceType);

        Map<? extends Class<?>, List<DataSourceColumnType>> columnJavaTypeMap =
                columnType.stream().collect(Collectors.groupingBy(DataSourceColumnType::getJavaType));
        Map<String, List<DataSourceColumnType>> columnTypeMap =
                columnType.stream().collect(Collectors.groupingBy(DataSourceColumnType::getDataSourceType));

        if (CharSequenceUtil.isBlank(fieldType)) {
            List<DataSourceColumnType> dataSourceColumnTypes = columnJavaTypeMap.get(fieldTypeClass);
            if (CollUtil.isEmpty(dataSourceColumnTypes)) {
                throw new IllegalArgumentException("There is no corresponding data mapping type: " + fieldTypeClass.getName());
            }
            DataSourceColumnType dataSourceColumnType;
            if (dataSourceColumnTypes.size() > 1) {
                dataSourceColumnType = dataSourceColumnTypes.stream().min(Comparator.comparing(DataSourceColumnType::getPriority))
                        .get();
            } else {
                dataSourceColumnType = dataSourceColumnTypes.get(0);
            }
            stringBuilder.append(dataSourceColumnType.getDataSourceType());
            if (length == -1) {
                if (dataSourceColumnType.getDefaultLength() != -1) {
                    stringBuilder.append("(").append(dataSourceColumnType.getDefaultLength()).append(")");
                }
            } else {
                stringBuilder.append("(").append(length).append(")");
            }
        } else {
            stringBuilder.append(fieldType);
            if (length == -1) {
                if (!columnTypeMap.containsKey(fieldType)) {
                    throw new IllegalArgumentException("There is no corresponding data mapping type: " + fieldType);
                }
                List<DataSourceColumnType> dataSourceColumnTypes = columnTypeMap.get(fieldType);
                DataSourceColumnType dataSourceColumnType;
                if (dataSourceColumnTypes.size() > 1) {
                    dataSourceColumnType = dataSourceColumnTypes.stream().min(Comparator.comparing(DataSourceColumnType::getPriority))
                            .get();
                } else {
                    dataSourceColumnType = dataSourceColumnTypes.get(0);
                }
                if (dataSourceColumnType.getDefaultLength() != -1) {
                    stringBuilder.append("(").append(dataSourceColumnType.getDefaultLength()).append(")");
                }
            } else {
                stringBuilder.append("(").append(length).append(")");
            }
        }
        return stringBuilder.toString();
    }

    /**
     * 获取对应数据源字段类型
     *
     * @return 数据源字段类型
     */
    public static List<DataSourceColumnType> getColumnType(String dataSourceType) {
        if (!TableResource.COLUMN_TYPE.containsKey(dataSourceType)) {
            throw new IllegalArgumentException("The corresponding data source " + dataSourceType + " column type information does not exist!");
        }
        return TableResource.COLUMN_TYPE.get(dataSourceType);
    }

}
